package com.zhz.mall.common.util.validate;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.util.ObjectUtil;
import com.zhz.mall.common.api.CommonResult;
import com.zhz.mall.common.api.ResultCode;
import com.zhz.mall.common.util.NotStackTraceException;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.HibernateValidator;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidationException;
import javax.validation.ValidatorFactory;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * ValidationUtils
 * <p>
 * 参数校验工具类
 *
 * @author zhouhengzhe
 */
public abstract class ValidationUtils extends org.springframework.validation.ValidationUtils {

    private static final Validator validator = getValidator();
    private static final javax.validation.Validator validator2 = getHibernateValidator(true);
    private static final javax.validation.Validator validatorAll = getHibernateValidator(false);

    private static Validator getValidator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        return new SpringValidatorAdapter(validatorFactory.getValidator());
    }

    /**
     * 记录所有的错误内容
     *
     * @return javax.validation.Validator
     */
    private static javax.validation.Validator getHibernateValidator(boolean failFast) {
        return Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(failFast)
                .buildValidatorFactory().getValidator();
    }

    public static BindingResult validateResult(Object object) {
        if (Objects.isNull(object)) {
            throw new ValidationException("Target object must not be null");
        }
        BindingResult validResult = new BeanPropertyBindingResult(object, "");
        invokeValidator(validator, object, validResult);
        return validResult;
    }

    public static <T> CommonResult<Void> validateObjects(List<T> var1, Class<?>... var2) {
        for (T t : var1) {
            CommonResult<Void> result = validateObject(t, var2);
            if (result.getCode() != ResultCode.SUCCESS.getCode()) {
                return result;
            }
        }
        return CommonResult.success();
    }

    public static <T> CommonResult<Void> validateObject(T var1, Class<?>... var2) {
        if (Objects.isNull(var1)) {
            return CommonResult.failed();
        }
        Set<ConstraintViolation<T>> violations = validatorArgs(var1, var2);
        if (!CollectionUtils.isEmpty(violations)) {
            return CommonResult.failed(violations.stream().iterator().next().getMessageTemplate());
        }
        return CommonResult.success();
    }

    public static <T> void verifyAll(T var1, Class<?>... var2) {
        if (Objects.isNull(var1)) {
            return;
        }
        Set<ConstraintViolation<T>> violations = validatorArgs(var1, var2);
        if (!CollectionUtils.isEmpty(violations)) {
            throw new NotStackTraceException.NotStackTraceRuntimeException(violations.stream().iterator().next().getMessageTemplate());
        }
    }

    public static <T> Set<ConstraintViolation<T>> validatorArgs(T var1, Class<?>... var2) {
        return validator2.validate(var1, var2);
    }

    /**
     * 校验所有数据，所有字段
     *
     * @param objects 数据集合
     * @param groups  校验分组
     * @param <T>     数据对象类型
     * @return true 校验通过， data -> null， false 校验失败 data -> [错误数据下标，错误信息列表]
     */
    public static <T> CommonResult<Map<Integer, List<String>>> validateAllData(List<T> objects, Class<?>... groups) {
        Map<Integer, List<String>> errMessage = new HashMap<>();
        // 判断是否需要 groups 分组
        Function<T, Set<ConstraintViolation<T>>> function = t -> validatorAll.validate(t, groups);
        if (groups == null || groups.length == 0) {
            function = validatorAll::validate;
        }
        for (int i = 0; i < objects.size(); i++) {
            T object = objects.get(i);
            Set<ConstraintViolation<T>> validate = function.apply(object);
            if (CollUtil.isEmpty(validate)) {
                continue;
            }
            List<String> collect = validate.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
            errMessage.put(i, collect);
        }
        if (CollUtil.isEmpty(errMessage)) {
            return CommonResult.success();
        }
        CommonResult<Map<Integer, List<String>>> fail = CommonResult.failed("校验不通过");
        fail.setData(errMessage);
        return fail;
    }


    public static void valid(Object object) throws ValidateException {
        BindingResult validResult = validateResult(object);
        if (validResult.hasErrors()) {
            ObjectError error = ObjectUtil.defaultIfNull(validResult.getFieldError(), validResult.getGlobalError());
            throw new ValidateException(error.getDefaultMessage(), object);
        }
    }


    /**
     * 校验所有数据，所有字段
     *
     * @param objects 数据集合
     * @param groups  校验分组
     * @param <T>     数据对象类型
     * @return true 校验通过， data -> null， false 校验失败 data -> [错误数据下标，错误信息列表]
     */
    public static <T> CommonResult<Map<Integer, List<FailMessage>>> validateAllDataPlus(List<T> objects, Class<?>... groups) {
        // 获取总体映射
        Class<?> dataClass = objects.get(0).getClass();
        Map<String, String> filedNameMap = new HashMap<>(16);
        for (Field declaredField : dataClass.getDeclaredFields()) {
            String name = declaredField.getName();
            ApiModelProperty annotation = declaredField.getAnnotation(ApiModelProperty.class);
            filedNameMap.put(name, annotation.value());
        }
        // 判断是否需要 groups 分组
        Function<T, Set<ConstraintViolation<T>>> function = t -> validatorAll.validate(t, groups);
        if (groups == null || groups.length == 0) {
            function = validatorAll::validate;
        }
        Map<Integer, List<FailMessage>> errMessage = new HashMap<>();
        for (int i = 0; i < objects.size(); i++) {
            T object = objects.get(i);
            Set<ConstraintViolation<T>> validate = function.apply(object);
            if (CollUtil.isEmpty(validate)) {
                continue;
            }
            int finalI = i;
            List<FailMessage> collect = validate.stream().map(temp -> FailMessage
                            .builder()
                            .index(finalI)
                            .apiModelPropertyName(filedNameMap.get(temp.getPropertyPath().toString()))
                            .className(temp.getRootBeanClass().getName())
                            .fieldName(temp.getPropertyPath().toString())
                            .errMessage(temp.getMessage())
                            .build())
                    .collect(Collectors.toList());
            errMessage.put(i, collect);
        }
        if (CollUtil.isEmpty(errMessage)) {
            return CommonResult.success();
        }
        CommonResult<Map<Integer, List<FailMessage>>> fail = CommonResult.failed("校验不通过");
        fail.setData(errMessage);
        return fail;
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class FailMessage {

        private int index;

        private String apiModelPropertyName;

        private String className;

        private String fieldName;

        private String errMessage;
    }

}
